#define LOG_TAG "WifiManager"
#include <cstring>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_pm.h"
#include "nvs_flash.h"

#include <c3log.h>
#include <Trace.h>

#include "WifiManager.h"

/*set the ssid and password via "idf.py menuconfig"*/
#define DEFAULT_SSID "ChinaNetM"
#define DEFAULT_PWD "yubao.zeng123456"

#define DEFAULT_LISTEN_INTERVAL_RATIO 5

//
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1

std::mutex gConn_m;
std::condition_variable gConn_cv;
bool gConnectTaskDone = false;
static bool gConnectStat = false;
static esp_ip4_addr_t gIp = {0};
static wifi_ap_record_t gConnectedApinfo = {0};
static double gLastTime = -1;

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    int ret = 0;
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        ret = esp_wifi_connect();
        if (ret != 0) {
            LOGE("wifi connected fail:%d", ret);
            gConnectStat = false;
        } else {
            gConnectStat = true;
        }
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        ret = esp_wifi_connect();
        if (ret != 0) {
            LOGE("wifi connected fail:%d", ret);
            gConnectStat = false;
        } else {
            gConnectStat = true;
        }
    } else if (event_base == IP_EVENT) {
        LOGE("wifi IP_EVENT, event_id:%d", event_id);
        if (event_id == IP_EVENT_STA_GOT_IP) {
            ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
            LOGI("got ip: " IPSTR, IP2STR(&event->ip_info.ip));
            gIp = event->ip_info.ip;
            //
        }
        std::unique_lock<std::mutex> lk(gConn_m);
        gConnectTaskDone = true;
        gConn_cv.notify_one();
    }
}

/*init wifi as sta and set power save mode*/
static void wifi_power_save(bool power_save) {
    TRACE_B();
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);


    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler,
        NULL, NULL));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler,
        NULL, NULL));

    wifi_config_t wifi_config = {
        .sta = {
            .listen_interval = DEFAULT_LISTEN_INTERVAL_RATIO,   //Beacon-Interval * DEFAULT_LISTEN_INTERVAL_RATIO
        },
    };

    memcpy(wifi_config.sta.ssid, DEFAULT_SSID, sizeof(DEFAULT_SSID));
    memcpy(wifi_config.sta.password, DEFAULT_PWD, sizeof(DEFAULT_PWD));

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    LOGI("wifi_init_sta finished.");
}

WifiManager::WifiManager(/* args */) {
}

WifiManager::~WifiManager() {
}

int WifiManager::connect(bool lowPower) {
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    wifi_power_save(lowPower);
    // wait connected
    {
        std::unique_lock<std::mutex> lk(gConn_m);
        gConn_cv.wait(lk, []{return gConnectTaskDone;});
    }
    return 0;
}

int WifiManager::RSSI(int span_second) {
    if (gLastTime > 0
        && get_sys_secend() - gLastTime < span_second) {
        return gConnectedApinfo.rssi;
    }
    gLastTime = get_sys_secend();
    int ret = esp_wifi_sta_get_ap_info(&gConnectedApinfo);
    LOGD("rssi:%d\n", gConnectedApinfo.rssi);
    if (ret == 0) {
        return gConnectedApinfo.rssi;
    } else {
        return -100;
    }
}

uint32_t WifiManager::getIp() {
    return gIp.addr;
}

uint32_t WifiManager::getIpStr(char* ip) {
    sprintf(ip,  "" IPSTR, IP2STR(&gIp));
    return 0;
}

bool WifiManager::getWifiConnected(){
    return gConnectStat;
}
